package com.zx.crowdfunding.service.impl;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.stereotype.Service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zx.crowdfunding.entity.Admin;
import com.zx.crowdfunding.entity.Menu;
import com.zx.crowdfunding.mapper.SessionMapper;
import com.zx.crowdfunding.security.SecurityAdmin;
import com.zx.crowdfunding.service.api.AdminService;
import com.zx.crowdfunding.service.api.AuthService;
import com.zx.crowdfunding.service.api.MenuService;
import com.zx.crowdfunding.service.api.SessionService;

/**
 * 会话 - Service接口实现类
 * @author 郑雪
 * @date 2021-12-18
 */
@Service
public class SessionServiceImpl implements SessionService {
	
	/**
	 * 会话Mapper接口
	 */
	@Autowired
	private SessionMapper sessionMapper;
	
	/**
	 * 菜单Service接口
	 */
	@Autowired
	private MenuService menuService;
	
	/**
	 * 权限Service接口
	 */
	@Autowired
	private AuthService authService;
	
	/**
	 * 管理员Service接口
	 */
	@Autowired
	private AdminService adminService;
	
	/**
	 * SpringSecurity在Session中的属性名
	 */
	private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
	
	/**
	 * 序列化和反序列化时需要用到
	 */
	private ConversionService conversionService;
	
	/**
	 * 构造函数
	 */
	public SessionServiceImpl() {
		this.conversionService = createDefaultConversionService();
	}

	@Override
	public PageInfo<Map<String, Object>> getPageInfo(String keyword, Integer pageNum,
			Integer pageSize) {
		
		// 1.调用PageHelper的静态方法开启分页功能
		PageHelper.startPage(pageNum, pageSize);
		
		// 2.执行查询
		List<Map<String, Object>> sessionList = sessionMapper.selectByKeyword(keyword);
		for (Map<String, Object> sessionMap : sessionList) {
			// 从会话中获取SpringSecurity的属性值
			byte[] blob = (byte[]) sessionMap.get("ATTRIBUTE_BYTES");
			SecurityContext securityContext = (SecurityContext) deserialize(blob);
			sessionMap.put("ATTRIBUTE_BYTES", securityContext);
		}
		
		// 3.封装到PageInfo对象中
		return new PageInfo<Map<String, Object>>(sessionList);
	}

	@Override
	public List<Map<String, Object>> getAll() {
		return sessionMapper.selectAll();
	}
	
	@Override
	public void editSessionForAddMenu(Menu menu) {
		
		// 获取全部会话集合
		List<Map<String, Object>> sessionList = sessionMapper.selectAll();
		
		// 如果会话集合为空
		if(sessionList == null || sessionList.size() == 0){
			return ;
		}
		
		// 上级菜单id
		Integer parentMenuId = menu.getPid();
		
		// 判断上级菜单的id是否等于1，因为当前系统中的菜单结构最多只有两级，
		// 所以如果上级菜单的id等于1，则说明上级菜单为根节点，当前新增加的菜单为一级菜单
		if(parentMenuId == 1){
			
			// 遍历会话集合
			for (Map<String, Object> sessionMap : sessionList) {
				
				// 会话id
				String sessionId = (String) sessionMap.get("SESSION_ID");
				
				// 从会话中获取SpringSecurity的属性值
				byte[] blob = (byte[]) sessionMap.get("ATTRIBUTE_BYTES");
				SecurityContext securityContext = (SecurityContext) deserialize(blob);
				
				// SpringSecurity的当前登录的对象
				SecurityAdmin securityAdmin = (SecurityAdmin) securityContext.getAuthentication().getPrincipal();
				
				// 在线用户的原始Admin对象
				Admin admin = securityAdmin.getOriginalAdmin();
				
				// 获取在线用户的菜单集合
				LinkedHashSet<Menu> menuSet = admin.getMenu();
				
				// 将当前新增加的菜单添加到菜单集合里
				menuSet.add(menu);
				
				// 更新Session中的属性值，修改数据库表中的数据
				String attributeName = SPRING_SECURITY_CONTEXT;
				byte[] attributeValue = serialize(securityContext);
				sessionMapper.updateSessionAttribute(sessionId, attributeName, attributeValue);
			
			}
			
		}
		// 如果当前新增加的菜单为二级菜单
		else {
			
			// 遍历会话集合
			for (Map<String, Object> sessionMap : sessionList) {
				
				// 会话id
				String sessionId = (String) sessionMap.get("SESSION_ID");
				
				// 从会话中获取SpringSecurity的属性值
				byte[] blob = (byte[]) sessionMap.get("ATTRIBUTE_BYTES");
				SecurityContext securityContext = (SecurityContext) deserialize(blob);
				
				// SpringSecurity的当前登录的对象
				SecurityAdmin securityAdmin = (SecurityAdmin) securityContext.getAuthentication().getPrincipal();
				
				// 在线用户的原始Admin对象
				Admin admin = securityAdmin.getOriginalAdmin();
				
				// 获取在线用户的菜单集合
				LinkedHashSet<Menu> menuSet = admin.getMenu();
				
				// 判断在线用户的菜单集合里是否已经有它的上级菜单了
				boolean isHasParentMenu = false;
				
				// 遍历查找
				for (Menu onlineMenu : menuSet) {
					// 如果在线用户的菜单集合里已经有上级菜单了
					if(onlineMenu.getId().equals(parentMenuId)){
						onlineMenu.getChildren().add(menu);
						isHasParentMenu = true;
						break;
					}
				}
				
				// 如果在线用户的菜单集合里没有上级菜单
				if(!isHasParentMenu){
					
					// 从数据库中获取上级菜单对象
					Menu parentMenu = menuService.getMenuById(parentMenuId);
					
					// 将当前新增加的菜单对象添加到上级菜单里
					parentMenu.getChildren().add(menu);
					
					// 将上级菜单添加到菜单集合里
					menuSet.add(parentMenu);
				}
				
				// 更新Session中的属性值，修改数据库表中的数据
				String attributeName = SPRING_SECURITY_CONTEXT;
				byte[] attributeValue = serialize(securityContext);
				sessionMapper.updateSessionAttribute(sessionId, attributeName, attributeValue);
			}
						
		}
	}

	@Override
	public void editSessionForEditMenu(Menu menu) {
		
		// 获取全部会话集合
		List<Map<String, Object>> sessionList = sessionMapper.selectAll();
		
		// 如果会话集合为空
		if(sessionList == null || sessionList.size() == 0){
			return ;
		}
		
		// 获取上级菜单id
		Integer parentMenuId = menu.getPid();
		
		// 判断上级菜单的id是否等于1，因为当前系统中的菜单结构最多只有两级，
		// 所以如果上级菜单的id等于1，则说明上级菜单为根节点，当前修改的菜单为一级菜单
		if(parentMenuId == 1){
			
			// 遍历会话集合
			for (Map<String, Object> sessionMap : sessionList) {
				
				// 会话id
				String sessionId = (String) sessionMap.get("SESSION_ID");
				
				// 从会话中获取SpringSecurity的属性值
				byte[] blob = (byte[]) sessionMap.get("ATTRIBUTE_BYTES");
				SecurityContext securityContext = (SecurityContext) deserialize(blob);
				
				// SpringSecurity的当前登录的对象
				SecurityAdmin securityAdmin = (SecurityAdmin) securityContext.getAuthentication().getPrincipal();
				
				// 在线用户的原始Admin对象
				Admin admin = securityAdmin.getOriginalAdmin();
				
				// 获取在线用户的菜单集合
				LinkedHashSet<Menu> menuSet = admin.getMenu();
				
				// 判断在线用户的菜单集合里是否已经有当前要修改的一级菜单
				boolean isHasMenu = false;
				
				// 遍历查找
				for (Menu onlineMenu : menuSet) {
					// 如果找到了，直接替换
					if(onlineMenu.getId().equals(menu.getId())){
						onlineMenu.setName(menu.getName());
						onlineMenu.setUrl(menu.getUrl());
						onlineMenu.setIcon(menu.getIcon());
						isHasMenu = true;
						break;
					}
				}
				
				// 如果在线用户的菜单集合里已经有当前要修改的菜单
				if(isHasMenu){
					
					// 更新Session中的属性值，修改数据库表中的数据
					String attributeName = SPRING_SECURITY_CONTEXT;
					byte[] attributeValue = serialize(securityContext);
					sessionMapper.updateSessionAttribute(sessionId, attributeName, attributeValue);
				}
			
			}
						
		}
		// 如果当前修改的菜单为二级菜单
		else {
			
			// 遍历会话集合
			for (Map<String, Object> sessionMap : sessionList) {
				
				// 会话id
				String sessionId = (String) sessionMap.get("SESSION_ID");
				
				// 从会话中获取SpringSecurity的属性值
				byte[] blob = (byte[]) sessionMap.get("ATTRIBUTE_BYTES");
				SecurityContext securityContext = (SecurityContext) deserialize(blob);
				
				// SpringSecurity的当前登录的对象
				SecurityAdmin securityAdmin = (SecurityAdmin) securityContext.getAuthentication().getPrincipal();
				
				// 在线用户的原始Admin对象
				Admin admin = securityAdmin.getOriginalAdmin();
				
				// 获取在线用户的菜单集合
				LinkedHashSet<Menu> menuSet = admin.getMenu();
				
				// 判断在线用户的菜单集合里是否已经有当前要修改的二级菜单
				boolean isHasMenu = false;
				
				// 遍历查找
				for (Menu onlineMenu : menuSet) {
					// 如果找到了它的上级菜单，继续查找
					if(onlineMenu.getId().equals(menu.getPid())){
						for (Menu menu2 : onlineMenu.getChildren()) {
							// 如果找到了，直接替换
							if(menu2.getId().equals(menu.getId())){
								menu2.setName(menu.getName());
								menu2.setUrl(menu.getUrl());
								menu2.setIcon(menu.getIcon());;
								isHasMenu = true;
								break;
							}
						}
						break;
					}
				}
				
				// 如果在线用户的菜单集合里已经有当前要修改的菜单
				if(isHasMenu){
					
					// 更新Session中的属性值，修改数据库表中的数据
					String attributeName = SPRING_SECURITY_CONTEXT;
					byte[] attributeValue = serialize(securityContext);
					sessionMapper.updateSessionAttribute(sessionId, attributeName, attributeValue);
				}
			
			}
						
		}
		
	}
	
	@Override
	public void editSessionForDeleteMenu(List<Integer> menuIdList) {
		
		// 获取全部会话集合
		List<Map<String, Object>> sessionList = sessionMapper.selectAll();
		
		// 遍历会话集合
		for (Map<String, Object> sessionMap : sessionList) {
			
			// 会话id
			String sessionId = (String) sessionMap.get("SESSION_ID");
			
			// 从会话中获取SpringSecurity的属性值
			byte[] blob = (byte[]) sessionMap.get("ATTRIBUTE_BYTES");
			SecurityContext securityContext = (SecurityContext) deserialize(blob);
			
			// SpringSecurity的当前登录的对象
			SecurityAdmin securityAdmin = (SecurityAdmin) securityContext.getAuthentication().getPrincipal();
			
			// 在线用户的原始Admin对象
			Admin admin = securityAdmin.getOriginalAdmin();
			
			// 获取在线用户的菜单集合
			LinkedHashSet<Menu> menuSet = admin.getMenu();
			
			// 判断在线用户的菜单集合里是否有当前要删除的菜单
			boolean isHasMenu = false;
			
			// 遍历查找
			Iterator<Menu> menuSetIterator = menuSet.iterator();
			while(menuSetIterator.hasNext()){
				Menu onlineMenu = menuSetIterator.next();
				Integer onlineMenuId = onlineMenu.getId();
				boolean isHasDeleteOneMenu = false;
				for (Integer deleteMenuId : menuIdList) {
					if(onlineMenuId.equals(deleteMenuId) ){
						menuSetIterator.remove();
						isHasMenu = true;
						isHasDeleteOneMenu = true;
						break;
					}
				}
				if(!isHasDeleteOneMenu){
					List<Menu> onlineChildrenMenuList = onlineMenu.getChildren();
					Iterator<Menu> onlineChildrenMenuListIterator = onlineChildrenMenuList.iterator();
					while(onlineChildrenMenuListIterator.hasNext()){
						Menu onlineChildrenMenu = onlineChildrenMenuListIterator.next();
						Integer onlineChildrenMenuId = onlineChildrenMenu.getId();
						for (Integer deleteMenuId : menuIdList) {
							if(onlineChildrenMenuId.equals(deleteMenuId)){
								onlineChildrenMenuListIterator.remove();
								isHasMenu = true;
								break;
							}
						}
					}
				}
				
			}
			
			// 如果在线用户的菜单集合里已经有当前要删除的菜单
			if(isHasMenu){
				
				// 更新Session中的属性值，修改数据库表中的数据
				String attributeName = SPRING_SECURITY_CONTEXT;
				byte[] attributeValue = serialize(securityContext);
				sessionMapper.updateSessionAttribute(sessionId, attributeName, attributeValue);
			}
		
		}
		
	}
	
	@Override
	public void editSessionForMenuSet() {
		// 获取全部会话集合
		List<Map<String, Object>> sessionList = sessionMapper.selectAll();
		
		// 遍历会话集合
		for (Map<String, Object> sessionMap : sessionList) {
			
			// 会话id
			String sessionId = (String) sessionMap.get("SESSION_ID");
			
			// 从会话中获取SpringSecurity的属性值
			byte[] blob = (byte[]) sessionMap.get("ATTRIBUTE_BYTES");
			SecurityContext securityContext = (SecurityContext) deserialize(blob);
			
			// SpringSecurity的当前登录的对象
			SecurityAdmin securityAdmin = (SecurityAdmin) securityContext.getAuthentication().getPrincipal();
			
			// 在线用户的原始Admin对象
			Admin admin = securityAdmin.getOriginalAdmin();
			
			// 根据adminId查询树形结构的菜单集合
			LinkedHashSet<Menu> menuSet = menuService.getMenuByAdminId(admin.getId());
			
			// 把查询出来的菜单列表存入管理员对象中
			admin.setMenu(menuSet);
			
			// 更新Session中的属性值，修改数据库表中的数据
			String attributeName = SPRING_SECURITY_CONTEXT;
			byte[] attributeValue = serialize(securityContext);
			sessionMapper.updateSessionAttribute(sessionId, attributeName, attributeValue);
			
		}
	}
	
	@Override
	public void editSessionForAuth() {
		// 获取全部会话集合
		List<Map<String, Object>> sessionList = sessionMapper.selectAll();
		
		// 遍历会话集合
		for (Map<String, Object> sessionMap : sessionList) {
			
			// 会话id
			String sessionId = (String) sessionMap.get("SESSION_ID");
			
			// 从会话中获取SpringSecurity的属性值
			byte[] blob = (byte[]) sessionMap.get("ATTRIBUTE_BYTES");
			SecurityContext securityContext = (SecurityContext) deserialize(blob);
			
			// SpringSecurity的当前登录的对象
			Authentication authentication = securityContext.getAuthentication();
			
			// 在线用户的原始Admin对象
			Admin oldAdmin = ((SecurityAdmin) authentication.getPrincipal()).getOriginalAdmin();
			
			// 根据adminId重新查询管理员对象，因为上面的admin对象中的密码已经被擦除了，这样会影响下面的重新创建对象
			Admin newAdmin = adminService.getAdminById(oldAdmin.getId());
			newAdmin.setMenu(oldAdmin.getMenu());
			
			// 根据adminId查询用户授权的集合
			List<GrantedAuthority> authorities = authService.getGrantedAuthorityListByAdminId(oldAdmin.getId());
			
			// 重新new一个新的自定义User对象
			SecurityAdmin user = new SecurityAdmin(newAdmin, authorities);
			
			// 重新new一个token，因为Authentication中的权限是不可变的.
			UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(), authorities);
			result.setDetails(authentication.getDetails());
			securityContext.setAuthentication(result);
			
			// 更新Session中的属性值，修改数据库表中的数据
			String attributeName = SPRING_SECURITY_CONTEXT;
			byte[] attributeValue = serialize(securityContext);
			sessionMapper.updateSessionAttribute(sessionId, attributeName, attributeValue);
			
		}
	}
	
	private static GenericConversionService createDefaultConversionService() {
		GenericConversionService converter = new GenericConversionService();
		converter.addConverter(Object.class, byte[].class,
				new SerializingConverter());
		converter.addConverter(byte[].class, Object.class,
				new DeserializingConverter());
		return converter;
	}

	/**
	 * 将一个Object对象序列化为字节数组
	 * @param attributeValue 需要序列化的Object对象（放入Session中的属性值）
	 * @return 字节数组
	 */
	private byte[] serialize(Object attributeValue){
		return (byte[]) this.conversionService.convert(attributeValue,
						TypeDescriptor.valueOf(Object.class),
						TypeDescriptor.valueOf(byte[].class));
	}
	
	/**
	 * 将一个字节数组反序列化为一个Object对象
	 * @param blob 字节数组（从数据库中查询出来的Session属性值）
	 * @return Object对象
	 */
	private Object deserialize(byte[] blob){
		return this.conversionService.convert(blob,
				TypeDescriptor.valueOf(byte[].class),
				TypeDescriptor.valueOf(Object.class));
	}

	
}
